#ifdef __KERNEL__
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/printk.h>
#include <linux/version.h>
#include <linux/of_platform.h>
#include <linux/pm_clock.h>
#include <linux/pm_domain.h>
#include <linux/pm_runtime.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/delay.h>
#include <linux/sched.h>
#include "ei_type.h"
#include "ei_common.h"
#include "ei_os.h"

#define VPU_AHB_GATE_NAME       "hvc_s_ahb_gate"
#define VPU_AHB_RESET_NAME      "hvc_s_ahb_reset"
#define VPU_AXI_GATE_NAME       "hvc_s_axi_gate"

#define ADPLL_TUNE1_OFFSET      (0x100AC)
#define ADPLL_TUNE0_OFFSET      (0x100A8)
#define ADPLL_FAC_OFFSET        (0x100A4)
#define ADPLL_CLK_CTRL_OFFSET   (0x100A0)
#define ADPLL_STAT_OFFSET       (0x100B0)

static struct clk *s_vpu_ahb_gate       = EI_NULL;
static struct clk *s_vpu_ahb_reset      = EI_NULL;
static struct clk *s_vpu_axi_gate       = EI_NULL;

static void __iomem *adpll_tune1_vir_addr;
static void __iomem *adpll_tune0_vir_addr;
static void __iomem *adpll_fac_vir_addr;
static void __iomem *adpll_clk_ctrl_vir_addr;
static void __iomem *adpll_stat_vir_addr;

struct platform_device *jpdev           = NULL;
struct device *g_vc_dev = NULL;

#else

#include <rtthread.h>
#include "irq_numbers.h"
#include "ei_os.h"
#include "ei_common.h"

#define HVC_BASE_ADDR            0x01b00000
#define HVC_BASE_SIZE            0x100000
#define HVC_FREQUENCE            336000000

#define PCRM_ADDR                0x0400a000
#define AHB_GATE_REG               (PCRM_ADDR + 0x204)
#define AHB_RESET_REG               (PCRM_ADDR + 0x224)
#define AXI_GATE_REG               (PCRM_ADDR + 0x340)

#define ADPLL_TUNE1              (HVC_BASE_ADDR + 0x100AC)
#define ADPLL_TUNE0              (HVC_BASE_ADDR + 0x100A8)
#define ADPLL_FAC                (HVC_BASE_ADDR + 0x100A4)
#define ADPLL_CLK_CTRL           (HVC_BASE_ADDR + 0x100A0)
#define ADPLL_STAT               (HVC_BASE_ADDR + 0x100B0)

static void *adpll_tune1_vir_addr;
static void *adpll_tune0_vir_addr;
static void *adpll_fac_vir_addr;
static void *adpll_clk_ctrl_vir_addr;
static void *adpll_stat_vir_addr;
static void *ahb_gate_reg_vir_addr;
static void *ahb_reset_reg_vir_addr;
static void *axi_gate_reg_vir_addr;

#endif

#ifdef __KERNEL__
#define READREG32(reg)            readl((void __iomem *)(reg))
#define WRITEREG32(reg, val)      writel((val), (void __iomem *)(reg))
#else
#ifndef READREG32
#define READREG32(reg)            os_readl(reg)
#endif
#ifndef WRITEREG32
#define WRITEREG32(reg, val)      os_writel(val, reg)
#endif
#endif

extern int VENC_VPU_ModInit(unsigned long phys_addr, unsigned long virt_addr, unsigned int size, EI_S32 irq);
extern void VENC_VPU_ModExit(void);
extern void pm_vpu_suspend(void);
extern void pm_vpu_resume(void);

unsigned int vpu_get_pthread_id(void)
{
#ifdef __KERNEL__
    return current->pid;
#else
    return rt_thread_self();
#endif
}

unsigned int vpu_get_tgid(void)
{
#ifdef __KERNEL__
    return current->tgid;
#else
    return rt_thread_self();
#endif
}

unsigned int vpu_time_after(unsigned long long a, unsigned long long b)
{
    return (a>=b) ? 1 : 0;
}

static int __clk_tune_msleep(int ms)
{
#ifdef __KERNEL__
	usleep_range(ms*1000, (ms+1)*1000);
#else
	os_msleep(ms);
#endif
    return 0;
}

int ei_jvc_probe(struct platform_device *pdev);
int ei_jvc_remove(struct platform_device *pdev);

int vpu_hw_reset(void)
{
        os_printk("not support hw reset\n");
            return 0;
}

unsigned int vpu_get_current_frequency(void)
{
    unsigned int cur_freq = 0, reg_var;

    reg_var = READREG32(adpll_fac_vir_addr);

    cur_freq = (reg_var & 0xff)*24/(((reg_var & 0xf00) >> 8) + 1);
    cur_freq = cur_freq <= 24 ? 0: cur_freq;

    return cur_freq;
}

static unsigned int get_vc_adpll_facfre(unsigned int frequency)
{
    unsigned int base_freq_mhz = frequency /1000000;
    unsigned int reg_var;
    unsigned int min_n = 0x10,max_n = 0x2a, min_m = 0, max_m = 0x3;
    unsigned int temp_n,temp_m;
    unsigned int  cur_freq;

    if (base_freq_mhz < 96)
    {
        base_freq_mhz = 96;
    }

    if (base_freq_mhz > 648)
    {
        base_freq_mhz = 648;
    }

    base_freq_mhz = ((base_freq_mhz + 5)/6)*6;

    for (temp_n = min_n; temp_n <= max_n; temp_n++)
    {

        for (temp_m = min_m; temp_m <= max_m; temp_m++)
        {
            reg_var = ((temp_n) | (temp_m << 8));
            cur_freq = (reg_var & 0xff)*24/(((reg_var & 0xf00) >> 8) + 1);
            if(cur_freq == base_freq_mhz)
            {
                return reg_var;
            }
        }
    }

    base_freq_mhz = ((base_freq_mhz + 23 )/24)*24;

    for (temp_n = min_n; temp_n <= max_n; temp_n++)
    {
        for (temp_m = min_m; temp_m <= max_m; temp_m++)
        {
            reg_var = ((temp_n) | (temp_m << 8));
            cur_freq = (reg_var & 0xff)*24/(((reg_var & 0xf00) >> 8) + 1);
            if(cur_freq == base_freq_mhz)
            {
                  return reg_var;
            }
        }

    }

    os_printk("clock set error\n");
    reg_var = 0x218;

    return reg_var;
}

static unsigned int g_frequency;

static void vc_adpll_enable(unsigned int frequency)
{
    unsigned int regval;
    unsigned int facfre;

    g_frequency = frequency;

    regval = 0x139f0000;
    WRITEREG32(adpll_tune0_vir_addr, regval);

    regval = 0x5 << 28 | 0x7 << 24 | 0x3 << 20 | 0x7 << 16 |
        0x4 << 13 | 0x1 << 12 | 0x1 << 8 | 0x3/*2*/ << 4;

    WRITEREG32(adpll_tune1_vir_addr, regval);

    facfre =  get_vc_adpll_facfre(frequency);
    WRITEREG32(adpll_fac_vir_addr, facfre);
	__clk_tune_msleep(5);
    regval = 0x1ul << 31 | facfre;
    WRITEREG32(adpll_fac_vir_addr, regval);

    while (1) {
        regval = READREG32(adpll_stat_vir_addr);
        if ((regval & 0x80008000) == 0x80008000)
            break;
		__clk_tune_msleep(5);
    }

    regval = 0x1ul << 31 | 0x1;
    WRITEREG32(adpll_clk_ctrl_vir_addr, regval);
}

static void vc_axi_ahb_gate_enable(int enable)
{
#ifdef __KERNEL__
    if (enable)
    {
        clk_disable_unprepare(s_vpu_ahb_reset);
        clk_prepare_enable(s_vpu_ahb_reset);
        clk_prepare_enable(s_vpu_ahb_gate);
        clk_prepare_enable(s_vpu_axi_gate);
    }else {
        clk_disable_unprepare(s_vpu_ahb_gate);
        clk_disable_unprepare(s_vpu_axi_gate);
    }
#else
    int regval;
    if (enable)
    {
        regval = READREG32(ahb_gate_reg_vir_addr);
        regval = regval | (1 << 10);
        WRITEREG32(ahb_gate_reg_vir_addr, regval);

        regval = READREG32(ahb_reset_reg_vir_addr);
        regval = regval & 0xfffffbff;
        WRITEREG32(ahb_reset_reg_vir_addr, regval);
        os_msleep(5);
        regval = regval | (1 << 10);
        WRITEREG32(ahb_reset_reg_vir_addr, regval);

        regval = READREG32(axi_gate_reg_vir_addr);
        regval = regval | (1 << 3);
        WRITEREG32(axi_gate_reg_vir_addr, regval);

    }else
    {
        regval = READREG32(ahb_gate_reg_vir_addr);
        regval = regval & ~(1 << 10);
        WRITEREG32(ahb_gate_reg_vir_addr, regval);

        regval = READREG32(axi_gate_reg_vir_addr);
        regval = regval & ~(1 << 3);
        WRITEREG32(axi_gate_reg_vir_addr, regval);
    }
#endif
}

void vpu_cclk_enable(void)
{
#ifdef CONFIG_PM
    pm_runtime_get_sync(g_vc_dev);
#else
    unsigned int regval;
    /* adpll enable */
    regval = 0x1ul << 31 | 0x1;
    WRITEREG32(adpll_clk_ctrl_vir_addr, regval);
#endif
}

void vpu_cclk_disable(void)
{
#ifdef CONFIG_PM
    pm_runtime_put_sync(g_vc_dev);
#else
    unsigned int regval;
    /* adpll disable */
    regval = 0x1;
    WRITEREG32(adpll_clk_ctrl_vir_addr, regval);
#endif
}

static int g_vc_clk_onoff = 0;
void vpu_clk_enable(void)
{

    if (g_vc_clk_onoff == 0)
    {
        vc_axi_ahb_gate_enable(1);
        vc_adpll_enable(g_frequency);
        vpu_cclk_enable();
    }

    g_vc_clk_onoff = 1;

}

void vpu_clk_disable(void)
{
    if (g_vc_clk_onoff == 1)
    {
        vpu_cclk_disable();
        vc_axi_ahb_gate_enable(0);
    }

    g_vc_clk_onoff = 0;

}
void vpu_clk_change_rate(unsigned int frequency)
{
    unsigned int regval;
    /* adpll disable */
    regval = 0x1;
    WRITEREG32(adpll_clk_ctrl_vir_addr, regval);

    vc_adpll_enable(frequency);
}

#ifdef __KERNEL__
#ifdef CONFIG_PM_GENERIC_DOMAINS
static int ei_vc_runtime_suspend(struct device *dev)
{
    unsigned int regval;

    /* adpll disable */
    regval = 0x1;
    WRITEREG32(adpll_clk_ctrl_vir_addr, regval);

    return 0;
}

static int ei_vc_runtime_resume(struct device *dev)
{
    unsigned int regval;

    /* adpll enable */
    regval = 0x1ul << 31 | 0x1;
    WRITEREG32(adpll_clk_ctrl_vir_addr, regval);
    return 0;
}
#endif

#ifdef CONFIG_PM_SLEEP
extern void pm_vpu_suspend(void);
extern void pm_vpu_resume(void);

static int ei_vc_suspend(struct device *dev)
{
    if (g_vc_clk_onoff == 0) {
        clk_prepare_enable(s_vpu_ahb_gate);
        clk_prepare_enable(s_vpu_axi_gate);
    }
    if (pm_runtime_status_suspended(dev))
        ei_vc_runtime_resume(dev);

    /* suspend */
    /* pm_vpu_suspend(); */

    ei_vc_runtime_suspend(dev);

    clk_disable_unprepare(s_vpu_ahb_gate);
    clk_disable_unprepare(s_vpu_axi_gate);

    return 0;
}

static int ei_vc_resume(struct device *dev)
{

    clk_disable_unprepare(s_vpu_ahb_reset);
    clk_prepare_enable(s_vpu_ahb_reset);

    clk_prepare_enable(s_vpu_ahb_gate);
    clk_prepare_enable(s_vpu_axi_gate);

    vc_adpll_enable(g_frequency);

    /* resume */
   /* pm_vpu_resume(); */

    if (pm_runtime_status_suspended(dev))
            ei_vc_runtime_suspend(dev);

    if (g_vc_clk_onoff == 0) {
        clk_disable_unprepare(s_vpu_ahb_gate);
        clk_disable_unprepare(s_vpu_axi_gate);
    }

    return 0;
}
#endif

#ifdef CONFIG_PM
static const struct dev_pm_ops ei_vc_pm_ops = {
#ifdef CONFIG_PM_GENERIC_DOMAINS
        SET_RUNTIME_PM_OPS(ei_vc_runtime_suspend,
            ei_vc_runtime_resume, NULL)
#endif
#ifdef CONFIG_PM_SLEEP
    SET_SYSTEM_SLEEP_PM_OPS(ei_vc_suspend, ei_vc_resume)
#endif
};
#endif

static int ei_vc_config_child_dev(struct device_node *parent)
{
    struct device_node *node;
    int ret = 0;

    for_each_available_child_of_node(parent, node) {
        struct platform_device *pdev;

        pdev = of_find_device_by_node(node);
        if (!pdev) {
            continue;
        }
        if (!strcmp(node->name, "ei_vc_jvc")) {
            ret = ei_jvc_probe(pdev);
            if (ret != 0)
                os_printk("ei_jvc_probe error\n");
            else
                jpdev = pdev;
        }
    }
    return ret;
}

static int ei_vc_probe(struct platform_device *pdev)
{
	struct device_node *np = pdev->dev.of_node;
    void __iomem *base = NULL;
    struct resource *iores;
    unsigned int frequency;
    unsigned long res_phys_addr, res_virt_addr;
    int irq = 0;
    int ret = 0;

    g_vc_dev = &pdev->dev;

	iores = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (!iores) {
		dev_err(&pdev->dev, "%s get resources fail\n", __func__);
		return -EINVAL;
	}

	base = devm_ioremap_resource(&pdev->dev, iores);
	if (IS_ERR(base)) {
		dev_err(&pdev->dev, "%s get io map fail\n", __func__);
		return PTR_ERR(base);
	}

    res_phys_addr = iores->start;
    res_virt_addr = (unsigned long)base;

    ret = of_property_read_u32(np, "clock-frequency", &frequency);
    if (ret < 0) {
        os_printk("vpu get clk freq failed\n");
        frequency = 240000000;
    }

    adpll_tune1_vir_addr = (void __iomem *)(base + ADPLL_TUNE1_OFFSET);
    adpll_tune0_vir_addr = (void __iomem *)(base + ADPLL_TUNE0_OFFSET);
    adpll_fac_vir_addr = (void __iomem *)(base + ADPLL_FAC_OFFSET);
    adpll_clk_ctrl_vir_addr = (void __iomem *)(base + ADPLL_CLK_CTRL_OFFSET);;
    adpll_stat_vir_addr = (void __iomem *)(ADPLL_STAT_OFFSET + base);

    s_vpu_ahb_gate = devm_clk_get(&pdev->dev, VPU_AHB_GATE_NAME);
    if (IS_ERR(s_vpu_ahb_gate)) {
        os_printk("get s_vpu_ahb_gate err\n");
    }

    s_vpu_ahb_reset = devm_clk_get(&pdev->dev, VPU_AHB_RESET_NAME);
    if (IS_ERR(s_vpu_ahb_reset)) {
        os_printk("get s_vpu_ahb_reset err\n");
    }

    s_vpu_axi_gate = devm_clk_get(&pdev->dev, VPU_AXI_GATE_NAME);
    if (IS_ERR(s_vpu_axi_gate)) {
        os_printk("get s_vpu_axi_gate err\n");
    }

    clk_prepare_enable(s_vpu_ahb_reset);

#ifdef CONFIG_PM_GENERIC_DOMAINS
    pm_runtime_enable(&pdev->dev);
    //pm_runtime_get_sync(&pdev->dev);
    //pm_runtime_get_noresume(&pdev->dev);
#endif

    g_vc_clk_onoff = 0;
    g_frequency = frequency;

    irq = platform_get_irq(pdev, 0);

    VENC_VPU_ModInit(res_phys_addr, res_virt_addr, (iores->end - iores->start + 1), irq);

    ret = ei_vc_config_child_dev(np);
    if (ret != 0) {
        os_printk("ei vc config child dev error\n");
        return -1;
    }

    os_printk("vc init ok\n");

    return 0;
}

static int ei_vc_remove(struct platform_device *pdev)
{

    os_printk("%s\n", "exit ok");

    VENC_VPU_ModExit();

#ifdef CONFIG_PM_GENERIC_DOMAINS
	pm_runtime_put_sync(&pdev->dev);
	pm_runtime_disable(&pdev->dev);
	pm_clk_remove_clk(&pdev->dev, s_vpu_ahb_gate);
	pm_clk_remove_clk(&pdev->dev, s_vpu_axi_gate);
#else
    clk_disable_unprepare(s_vpu_axi_gate);
    clk_disable_unprepare(s_vpu_ahb_gate);
#endif
    clk_disable_unprepare(s_vpu_ahb_reset);

    devm_clk_put(&pdev->dev, s_vpu_axi_gate);
    devm_clk_put(&pdev->dev, s_vpu_ahb_gate);
    devm_clk_put(&pdev->dev, s_vpu_ahb_reset);

    if (jpdev != NULL)
        ei_jvc_remove(jpdev);

    return 0;
}


static const struct of_device_id ei_vc_match[] = {
    { .compatible = "ei, ei-vc" },
    {},
};

MODULE_DEVICE_TABLE(of, ei_vc_match);

static struct platform_driver ei_vc_driver = {
    .probe          = ei_vc_probe,
    .remove         = ei_vc_remove,
    .driver         =
    {
        .name           = "ei_vc",
        .of_match_table = ei_vc_match,
#ifdef CONFIG_PM
        .pm             = &ei_vc_pm_ops,
#endif
    },
};

os_module_platform_driver(ei_vc_driver);

MODULE_LICENSE("GPL");

#else
int vc_driver_init()
{
    unsigned long res_phy_addr;
    unsigned long res_vir_addr;
    unsigned int frequency;
    unsigned int facfre;

    int res_size;
    int irq = 0;
    int ret = 0;
    unsigned int regval;
    int retry_cnt = 10, times_retry = 0;

    os_printk("ei_vc_probe start \n");

    res_phy_addr = HVC_BASE_ADDR;
    res_size = HVC_BASE_SIZE;
    res_vir_addr = (unsigned long)os_ioremap_nocache(res_phy_addr, res_size);

    frequency = HVC_FREQUENCE;
    os_printk("frequency:%d\n", frequency);

    adpll_tune1_vir_addr = os_ioremap_nocache(ADPLL_TUNE1, 0x4);
    adpll_tune0_vir_addr = os_ioremap_nocache(ADPLL_TUNE0, 0x4);
    adpll_fac_vir_addr = os_ioremap_nocache(ADPLL_FAC, 0x4);
    adpll_clk_ctrl_vir_addr = os_ioremap_nocache(ADPLL_CLK_CTRL, 0x4);
    adpll_stat_vir_addr = os_ioremap_nocache(ADPLL_STAT, 0x4);


    ahb_gate_reg_vir_addr = os_ioremap_nocache(AHB_GATE_REG, 0x4);
    ahb_reset_reg_vir_addr = os_ioremap_nocache(AHB_RESET_REG, 0x4);
    axi_gate_reg_vir_addr = os_ioremap_nocache(AXI_GATE_REG, 0x4);

    g_frequency = frequency;
    g_vc_clk_onoff = 0;

    irq = INT_HVC_S;

    VENC_VPU_ModInit(res_phy_addr, res_vir_addr, res_size, irq);

    return 0;
}

#endif

